package com.heima.wemedia.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.common.constants.cloud.tencent.TencentConstants;
import com.heima.common.constants.message.PublishArticleConstants;
import com.heima.common.constants.wemedia.WemediaConstants;
import com.heima.common.exception.CustException;
import com.heima.feigns.AdminFeign;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.tencentcloud.scan.ContentSecurityScan;
import com.heima.utils.common.SensitiveWordUtil;
import com.heima.wemedia.service.WmNewsAutoScanService;
import com.heima.wemedia.service.WmNewsService;
import com.tencentcloudapi.ims.v20201229.models.ImageModerationResponse;
import com.tencentcloudapi.tms.v20201229.models.TextModerationResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author XHui
 * @Since 2024/2/14 9:30
 * @Version 1.0
 */
@Slf4j
@Service
public class WmNewsAutoScanServiceImpl implements WmNewsAutoScanService {

    @Resource
    private WmNewsService wmNewsService;

    @Value("${file.oss.web-site}")
    private String webSite;

    /**
     * 自动扫描 WM 新闻，该方法由消息队列进行出发
     *
     * @param id 编号
     */
    @Override
    public void autoScanWmNews(Integer id) {
        log.info("文章自动审核方法触发 待审核的文章id: {}", id);

        // 1. 校验参数
        if (id == null)
            CustException.cust(AppHttpCodeEnum.PARAM_INVALID, "待审核的文章id不能为空");

        // 2. 根据id查询该新闻是否存在
        WmNews queriedWmNewsById = wmNewsService.getById(id);
        if (queriedWmNewsById == null) {
            CustException.cust(AppHttpCodeEnum.DATA_NOT_EXIST, "该ID对应的的文章不存在");
        }

        // 3. 状态处于待审核(1)，才需要进行自动审核
        Short status = queriedWmNewsById.getStatus();
        if (!WemediaConstants.WM_NEWS_SUMMIT_STATUS.equals(status)) {
            log.info("当前文章状态为: {} ，无需审核", status);
            return;
        }

        // TODO 4. 抽取文章中所有的文本内容 和 所有的图片内容
        Map<String, Object> contentAndImageMap = extractTextAndImages(queriedWmNewsById);

        // 5. 根据id获取新闻内容，对其进行自定义敏感词检测，若检测到违规，则审核失败(修改状态为2)
        String content = (String) contentAndImageMap.get("content");
        boolean pass = handleSensitiveWords(content, queriedWmNewsById);
        if (!pass) { // 存在敏感词
            log.info("文章审核未通过，新闻内容中存在敏感词");
            return;
        }

        // 6. 使用腾讯云的文本审核，不确定和调用失败 人工审核，
        // 根据suggestion：pass block 进行处理 继续下一步
        boolean textScanFlag = handleTextScanByTencentCloud(content, queriedWmNewsById);
        if (!textScanFlag) {
            log.info("文章审核未通过，原因：内容中包含违规词汇");
            return;
        }

        // 7. 使用腾讯云的图片审核，不确定和调用失败 人工审核，
        // 根据suggestion：pass block 进行处理 继续下一步
        Boolean imageScanFlag = handleImagesScanByTencentCloud((List<String>) contentAndImageMap.get("images"), queriedWmNewsById);
        if (!imageScanFlag) { // 图片中出现违规信息
            log.info("文章审核未通过，原因：图片中包含违规信息");
            return;
        }
        // 8. 新闻状态改为8，自动审核已通过
        updateWmNews(WmNews.Status.SUCCESS.getCode(), "自动审核通过", queriedWmNewsById);

        // TODO 9. 根据文章的发布时间，发送延迟消息，用于定时发布文章（9 已发布）
        delayPublishWmNews(queriedWmNewsById);
    }

    @Resource
    private RabbitTemplate rabbitTemplate;

    private void delayPublishWmNews(WmNews wmNews) {
        // 1. 处理时间格式
        // 1.1 获取发布时间
        long publishTime = wmNews.getPublishTime().getTime();
        // 1.2 获取当前时间
        long now = new Date().getTime();
        // 1.3 计算剩余时间
        long remainTime = publishTime - now;

        // 2. 发送消息 by rabbitTemplate
        rabbitTemplate.convertAndSend(
                PublishArticleConstants.DELAY_DIRECT_EXCHANGE, // 指定交换机名称
                PublishArticleConstants.PUBLISH_ARTICLE_ROUTE_KEY, // 指定路由键名称
                // 为了避免延迟队列中的wmNews可能与主表中的wmNews不一致，故将id作为消息体发送给delayed队列
                wmNews.getId(),
                message -> {
                    MessageProperties properties = message.getMessageProperties();
                    properties.setHeader("x-delay", remainTime <=0 ? 0 : remainTime);
                    return message;
                }
        );
        log.info("文章自动审核通过，已发送到delayed队列，等待定时发布, 文章id: {}, 当前时间：{}", wmNews.getId(), LocalDateTime.now());
    }

    private Boolean handleImagesScanByTencentCloud(List<String> imageUrls,
                                                   WmNews wmNews) {
        boolean flag = false;
        try {
            ImageModerationResponse imr = contentSecurityScan.imageScanBatch(imageUrls);
            switch (imr.getSuggestion()) {
                case TencentConstants.CONTENT_SUGGESTION_BLOCK:
                    updateWmNews(WmNews.Status.FAIL.getCode(), "图片中含有违规信息：" + imr.getSubLabel(), wmNews);
                    break;
                case TencentConstants.CONTENT_SUGGESTION_REVIEW:
                    updateWmNews(WmNews.Status.ADMIN_AUTH.getCode(), "图片中含有疑似违规信息，转为人工审核", wmNews);
                    break;
                case TencentConstants.CONTENT_SUGGESTION_PASS:
                    flag = true;
                    break;
                default:
                    // 转为人工
                    updateWmNews(WmNews.Status.ADMIN_AUTH.getCode(), "腾讯云图片内容安全调用状态异常，转为人工审核", wmNews);
                    break;
            }
        } catch (Exception e) {
            updateWmNews(WmNews.Status.ADMIN_AUTH.getCode(), "腾讯云图片内容安全调用失败，转为人工审核", wmNews);
            throw new RuntimeException(e);
        }
        return flag;
    }

    @Resource
    ContentSecurityScan contentSecurityScan;

    /**
     * 处理腾讯云文本扫描
     *
     * @param content 内容
     * @param wmNews  WM新闻
     * @return boolean
     */
    private boolean handleTextScanByTencentCloud(String content, WmNews wmNews) {
        boolean flag = false;
        try {
            TextModerationResponse textResp = contentSecurityScan.textScan(content);
            switch (textResp.getSuggestion()) {
                case TencentConstants.CONTENT_SUGGESTION_BLOCK:
                    updateWmNews(WmNews.Status.FAIL.getCode(), "文本中含有违规词汇：" + Arrays.toString(textResp.getKeywords()), wmNews);
                    break;
                case TencentConstants.CONTENT_SUGGESTION_REVIEW:
                    updateWmNews(WmNews.Status.ADMIN_AUTH.getCode(), "文本中含有不确定内容，转为人工审核", wmNews);
                    break;
                case TencentConstants.CONTENT_SUGGESTION_PASS:
                    flag = true;
                    break;
                default:
                    // 转为人工
                    updateWmNews(WmNews.Status.ADMIN_AUTH.getCode(), "腾讯云文本内容安全调用状态异常，转为人工审核", wmNews);
                    break;
            }
        } catch (Exception e) {
            log.error("调用腾讯云文本审核接口失败，原因：{}, 转为人工审核", e.getMessage());
            updateWmNews(WmNews.Status.ADMIN_AUTH.getCode(), "腾讯云内容安全调用失败，转为人工审核", wmNews);
            throw new RuntimeException(e);
        }
        return flag;
    }

    @Resource
    private AdminFeign adminFeign;

    /**
     * 检测敏感词
     *
     * @param content 文章的所有内容，包括标题
     * @param wmNews  按 ID 查询 WM 新闻
     * @return boolean
     */
    private boolean handleSensitiveWords(String content, WmNews wmNews) {
        boolean flag = false;
        // 1. 先通过Feign跨服务调用管理员微服务，获取敏感词列表
        ResponseResult<List<String>> sensitiveWordResult = adminFeign.getAllSensitiveWords();
        if (!sensitiveWordResult.checkCode()) {
            CustException.cust(AppHttpCodeEnum.REMOTE_SERVER_ERROR, sensitiveWordResult.getErrorMessage());
        }
        List<String> sensitiveWords = sensitiveWordResult.getData();

        // 2. 将敏感词列表转换为DFA数据模型
        SensitiveWordUtil.initMap(sensitiveWords);

        // 2.1 使用敏感词检测工具类
        Map<String, Integer> matchedWordsMap = SensitiveWordUtil.matchWords(content);
        if (CollectionUtils.isEmpty(matchedWordsMap)) { // 未匹配到敏感词，返回 true 检查通过
            return true;
        } else { // 匹配到敏感词
            wmNews.setStatus(WmNews.Status.FAIL.getCode());
            updateWmNews(WmNews.Status.FAIL.getCode(), "文章内容中存在敏感词: " + matchedWordsMap, wmNews);
            return flag;
        }
    }

    private void updateWmNews(Short status, String reason, WmNews wmNews) {
        wmNews.setStatus(status);
        wmNews.setReason(reason);
        boolean updateFlag = wmNewsService.updateById(wmNews);
        if (!updateFlag) {
            CustException.cust(AppHttpCodeEnum.SERVER_ERROR, "更新文章状态失败");
        }
    }

    /**
     * 提取文本和图像
     *
     * @param wmNews 按 ID 查询 WM 新闻
     * @return map<字符串 ， 对象>
     */
    private Map<String, Object> extractTextAndImages(WmNews wmNews) {
        // 1. 判断内容不能为空，并转为List<Map>
        String contentJson = wmNews.getContent();
        if (StringUtils.isBlank(contentJson)) {
            CustException.cust(AppHttpCodeEnum.PARAM_INVALID, "文章内容不能为空");
        }
        Map<String, Object> resultMap = new HashMap<>();

        // 2. 抽取内容中的所有文本内容，拼接成一个字符串 [{type: '', value: ''}, {}]
        List<Map> contentMapList = JSON.parseArray(contentJson, Map.class);
        String contentString = contentMapList.stream()
                .filter(map -> "text".equals(map.get("type"))) // 过滤出{type: '', value: ''}
                .map(map -> map.get("value").toString())  // 过滤出多个value
                .collect(Collectors.joining("_hmtt_")); // 将文本内容拼接成一个字符串，并添加分隔符
        // 2.1 将文本内容 和 标题拼接成一个字符串
        contentString += String.join("_hmtt_") + wmNews.getTitle();
        // 2.2 将总的文本内容装入 map
        resultMap.put("content", contentString);

        // 3. 抽取文章中所有的图片列表
        // 3.1 抽取content中所有的图片，得到图片列表
        List<String> finalImageUrls = contentMapList.stream()
                .filter(map -> "image".equals(map.get("type"))) // 过滤出{type: '', value: ''}
                .map(map -> map.get("value").toString())
                .collect(Collectors.toList());

        // 3.2 抽取封面中所有的图片，得到图片列表
        // url1 url2 url3 封面图片是不带域名前缀的
        String coverImageUrl = wmNews.getImages();
        if (StringUtils.isNotBlank(coverImageUrl)) { // 封面图片可能为空，也可能不为空
            String[] strings = coverImageUrl.split(",");
            List<String> coverImageList = Arrays.stream(strings)
                    .map(url -> webSite + url)
                    .collect(Collectors.toList());
            // 3.3 合并，内容图片 和 封面图片
            finalImageUrls.addAll(coverImageList);
        }

        // 3.4 去除重复的图片
        finalImageUrls = finalImageUrls.stream()
                .distinct()
                .collect(Collectors.toList());
        // 3.5 将所有的imageUrls装入map
        resultMap.put("images", finalImageUrls);
        return resultMap;
    }
}
